package tcp;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

//实现一个tcp回显服务器
public class Tcpserver {
    //创建一个ServerSoket对象用于与客户端连接
    ServerSocket socket=null;
    //给soket绑定端口号
    public Tcpserver(int port) throws IOException {
        socket=new ServerSocket(port);
    }
    public void start() throws IOException {
        //打印服务器启动日志
        System.out.println("TCP服务器已启动");
        //创建线程池
        ExecutorService threadPool= Executors.newCachedThreadPool();
        while(true){
            //与客户端建立连接，连接成功返回一个Socket对象，否则阻塞等待
            Socket clientSocket=socket.accept();
            //《版本1》无法实现同时处理多个客户端
            //因为单线程场景下，在执行下方方法时会造成阻塞等待
            //此时就无法执行到Socket clientSocket=socket.accept();
            //其他客户端也就无法与服务器建立连接也就无法
            //proConnect(clientSocket);

            //《版本2》利用多线程解决了多个客户端因为某个客户端执行proConnect(clientSocket);
            //过程中发生阻塞等待 ，而导致的其他客户端与服务器的连接失败问题
//            Thread thread=new Thread(()->{
//                try {
//                    proConnect(clientSocket);
//                } catch (IOException e) {
//                    e.printStackTrace();
//                }
//            });
//            thread.start();


            //《版本3》使用线程池来进行与多个客户端交互
            //这样相比于版本2的优点在于避免了在高并发的场景下，要频繁的创建和销毁线程
            //使用线程池提高运行的效率
            threadPool.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        proConnect(clientSocket);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            });

            //可以在此处销毁ClientScoket但是为了防止发生异常保险还是写在
            //proConnect方法的finally语句里
        }
    }
    public void proConnect(Socket clientSocket) throws IOException {
        //打印客户端与服务器的连接日志
        System.out.printf("[%s %d] 建立连接\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
            //获取clientSocket的输入输出流
            try(InputStream inputStream=clientSocket.getInputStream();
                OutputStream outputStream= clientSocket.getOutputStream()){

                //对输入输出流进行包装，方便我们数据的读写
                Scanner scannerNet=new Scanner(inputStream);
                PrintWriter printWriter=new PrintWriter(outputStream);
                //长连接(通过while循环与服务器发生多次交互)
                while(true){
                    //当客户端进程结束或者执行到文件末尾，hasNext()都会返回false
                    //其他情况下返回true
                    if(!scannerNet.hasNext()){
                        System.out.println("客户端断开连接");
                        break;
                    }
                    //数据交互
                    String request=scannerNet.next();
                    String respon=process(request);
                    printWriter.println(respon);
                    //刷新缓存区
                    printWriter.flush();

                    System.out.printf("[%s %d]  request:%s  respon:%s\n",clientSocket.getInetAddress().toString()
                    ,clientSocket.getPort(),request,respon);

                }

            } finally {
                //防止异常导致clientSocket无法关闭导致内存泄露
                clientSocket.close();
            }
    }

    //与客户端进行交互
    public String process(String request) throws IOException {
        return request;
    }

    public static void main(String[] args) throws IOException {
        Tcpserver tcpserver=new Tcpserver(8000);
        tcpserver.start();
    }
}
